package com.zeyu.framework.monitors.webserver;

import com.beust.jcommander.internal.Lists;
import com.zeyu.framework.monitors.Collector;
import com.zeyu.framework.monitors.webserver.entity.MemoryUsageStatus;
import com.zeyu.framework.monitors.webserver.entity.RequestProcessorStatus;
import com.zeyu.framework.monitors.webserver.entity.WebServerStatus;
import com.zeyu.framework.monitors.webserver.service.WebServerService;
import com.zeyu.framework.utils.DateUtils;
import com.zeyu.framework.utils.StringUtils;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.ServerInfo;
import org.apache.tomcat.util.modeler.Registry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.servlet.ServletException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.RuntimeMXBean;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;

/**
 * 执行WebServer的采集任务，将来可能会被任务调度使用，都是独立的方法
 * Created by zeyuphoenix on 16/8/26.
 */
@Component
public class WebServerCollector implements Collector, NotificationListener, EnvironmentAware {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(WebServerCollector.class);

    // ================================================================
    // Fields
    // ================================================================

    // 最后一次采集的web server状态
    private WebServerStatus webServerStatus;

    @Autowired
    private WebServerService webServerService;

    // context path
    private String contextPath = "/";
    // server port
    private String port = "80";

    /**
     * MBean server.
     */
    protected MBeanServer mBeanServer = null;


    /**
     * Vector of protocol handlers object names.
     */
    protected final Vector<ObjectName> protocolHandlers = new Vector<>();


    /**
     * Vector of thread pools object names.
     */
    protected final Vector<ObjectName> threadPools = new Vector<>();


    /**
     * Vector of request processors object names.
     */
    protected final Vector<ObjectName> requestProcessors = new Vector<>();


    /**
     * Vector of global request processors object names.
     */
    protected final Vector<ObjectName> globalRequestProcessors = new Vector<>();

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    @Override
    public void collect() {
        logger.info("Web Server收集任务启动...");

        // 获取当前的状态
        webServerStatus = getCurrentWebServerStatus();

        webServerStatus.setTime(new Date());

        // 保存信息到数据库
        webServerService.save(this.webServerStatus);
    }

    @Override
    public void setEnvironment(Environment environment) {
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment);
        contextPath = propertyResolver.getProperty("server.context-path", "/");
        port = propertyResolver.getProperty("server.port", "80");
    }

    @PostConstruct
    public void init() throws ServletException {

        // Retrieve the MBean server
        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();

        try {

            // Query protocol handlers
            String onStr = "*:type=ProtocolHandler,*";
            ObjectName objectName = new ObjectName(onStr);
            Set<ObjectInstance> set = mBeanServer.queryMBeans(objectName, null);
            Iterator<ObjectInstance> iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = iterator.next();
                protocolHandlers.addElement(oi.getObjectName());
            }

            // Query Thread Pools
            onStr = "*:type=ThreadPool,*";
            objectName = new ObjectName(onStr);
            set = mBeanServer.queryMBeans(objectName, null);
            iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = iterator.next();
                threadPools.addElement(oi.getObjectName());
            }

            // Query Global Request Processors
            onStr = "*:type=GlobalRequestProcessor,*";
            objectName = new ObjectName(onStr);
            set = mBeanServer.queryMBeans(objectName, null);
            iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = iterator.next();
                globalRequestProcessors.addElement(oi.getObjectName());
            }

            // Query Request Processors
            onStr = "*:type=RequestProcessor,*";
            objectName = new ObjectName(onStr);
            set = mBeanServer.queryMBeans(objectName, null);
            iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = iterator.next();
                requestProcessors.addElement(oi.getObjectName());
            }

            // Register with MBean server
            onStr = "JMImplementation:type=MBeanServerDelegate";
            objectName = new ObjectName(onStr);
            mBeanServer.addNotificationListener(objectName, this, null, null);

        } catch (Exception e) {
            logger.error("init error: ", e);
        }

    }

    @Override
    public void handleNotification(Notification notification,
                                   java.lang.Object handback) {

        if (notification instanceof MBeanServerNotification) {
            ObjectName objectName =
                    ((MBeanServerNotification) notification).getMBeanName();
            if (notification.getType().equals
                    (MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
                String type = objectName.getKeyProperty("type");
                if (type != null) {
                    switch (type) {
                        case "ProtocolHandler":
                            protocolHandlers.addElement(objectName);
                            break;
                        case "ThreadPool":
                            threadPools.addElement(objectName);
                            break;
                        case "GlobalRequestProcessor":
                            globalRequestProcessors.addElement(objectName);
                            break;
                        case "RequestProcessor":
                            requestProcessors.addElement(objectName);
                            break;
                    }
                }
            } else if (notification.getType().equals
                    (MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
                String type = objectName.getKeyProperty("type");
                if (type != null) {
                    switch (type) {
                        case "ProtocolHandler":
                            protocolHandlers.removeElement(objectName);
                            break;
                        case "ThreadPool":
                            threadPools.removeElement(objectName);
                            break;
                        case "GlobalRequestProcessor":
                            globalRequestProcessors.removeElement(objectName);
                            break;
                        case "RequestProcessor":
                            requestProcessors.removeElement(objectName);
                            break;
                    }
                }
            }
        }

    }

    @PreDestroy
    public void destroy() {

        // Unregister with MBean server
        String onStr = "JMImplementation:type=MBeanServerDelegate";
        ObjectName objectName;
        try {
            objectName = new ObjectName(onStr);
            mBeanServer.removeNotificationListener(objectName, this, null, null);
        } catch (Exception e) {
           logger.error("destroy error: ", e);
        }
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * 获取最后一次采集的web server状态
     */
    public WebServerStatus getWebServerStatus() {
        return this.webServerStatus;
    }

    /**
     * 获取当前web server的状态
     *
     * @return 状态
     */
    public WebServerStatus getCurrentWebServerStatus() {

        WebServerStatus webServerStatus = new WebServerStatus();
        try {

            logger.debug("Web server status: ok");
            webServerStatus.setStatus(true);

            // 路径信息
            webServerStatus.setContextPath(contextPath);

            // 地址信息
            try {
                InetAddress address = InetAddress.getLocalHost();
                webServerStatus.setHost(address.getHostName());
                webServerStatus.setIp(address.getHostAddress());
            } catch (UnknownHostException e) {
                webServerStatus.setHost("-");
                webServerStatus.setIp("-");
            }
            // jvm信息
            webServerStatus.setJvmVendor(System.getProperty("java.vm.vendor"));
            webServerStatus.setJvmVersion(System.getProperty("java.runtime.version"));
            // os相关信息
            webServerStatus.setOsArch(System.getProperty("os.arch"));
            webServerStatus.setOsName(System.getProperty("os.name"));
            webServerStatus.setOsVersion(System.getProperty("os.version"));
            // server 信息
            webServerStatus.setServerInfo(ServerInfo.getServerInfo());

            // 内存信息
            webServerStatus.setFreeMemory(Runtime.getRuntime().freeMemory());
            webServerStatus.setMaxMemory(Runtime.getRuntime().maxMemory());
            webServerStatus.setTotalMemory(Runtime.getRuntime().totalMemory());

            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
            long uptimeInMillis = runtimeMXBean.getUptime();
            long startTime = runtimeMXBean.getStartTime();

            long totalStartedThreadCount = ManagementFactory.getThreadMXBean().getTotalStartedThreadCount();

            // 运行时长
            webServerStatus.setUptime(uptimeInMillis);
            // 启动时间
            webServerStatus.setStartTime(DateUtils.formatDateTime(new Date(startTime)));

            // 启动线程数
            webServerStatus.setTotalStartedThreadCount((int) totalStartedThreadCount);

            // 获取线程名
            ObjectName currName = null;
            Enumeration<ObjectName> enumeration = threadPools.elements();
            while (enumeration.hasMoreElements()) {
                ObjectName objectName = enumeration.nextElement();

                Object tport = mBeanServer.getAttribute(objectName, "port");
                if (tport != null && StringUtils.equals(tport.toString(), port)) {
                    currName = objectName;
                    break;
                }
            }
            if (currName != null) {
                // 线程
                webServerStatus.setMaxThreads(Integer.valueOf(mBeanServer.getAttribute(currName, "maxThreads").toString()));
                webServerStatus.setCurrentThreadCount(Integer.valueOf(mBeanServer.getAttribute(currName, "currentThreadCount").toString()));
                webServerStatus.setCurrentThreadsBusy(Integer.valueOf(mBeanServer.getAttribute(currName, "currentThreadsBusy").toString()));
                try {
                    Object value = mBeanServer.getAttribute(currName, "keepAliveCount");
                    webServerStatus.setKeepAliveCount(Integer.valueOf(value.toString()));
                } catch (Exception e) {
                    // Ignore
                }

                // 请求和字节数
                ObjectName grpName = null;

                Enumeration<ObjectName> enumerationx =
                        globalRequestProcessors.elements();
                String name = currName.getKeyProperty("name");
                while (enumerationx.hasMoreElements()) {
                    ObjectName objectNamex = enumerationx.nextElement();
                    if (name.equals(objectNamex.getKeyProperty("name"))) {
                        grpName = objectNamex;
                        break;
                    }
                }

                if (grpName != null) {
                    // 请求数
                    webServerStatus.setErrorCount(Long.valueOf(mBeanServer.getAttribute(grpName, "errorCount").toString()));
                    webServerStatus.setRequestCount(Long.valueOf(mBeanServer.getAttribute(grpName, "requestCount").toString()));
                    webServerStatus.setProcessingTime(Long.valueOf(mBeanServer.getAttribute
                            (grpName, "processingTime").toString()));
                    webServerStatus.setMaxTime(Long.valueOf(mBeanServer.getAttribute
                            (grpName, "maxTime").toString()));

                    // 接收字节数
                    webServerStatus.setBytesReceived(Long.valueOf(mBeanServer.getAttribute
                            (grpName, "bytesReceived").toString()));
                    webServerStatus.setBytesSent(Long.valueOf(mBeanServer.getAttribute
                            (grpName, "bytesSent").toString()));

                } else {
                    // 接收字节数
                    webServerStatus.setBytesReceived(0);
                    webServerStatus.setBytesSent(0);

                    // 请求数
                    webServerStatus.setErrorCount(0);
                    webServerStatus.setRequestCount(0);
                    webServerStatus.setProcessingTime(0);
                    webServerStatus.setMaxTime(0);
                }

            } else {
                // 接收字节数
                webServerStatus.setBytesReceived(0);
                webServerStatus.setBytesSent(0);

                // 线程
                webServerStatus.setMaxThreads(0);
                webServerStatus.setKeepAliveCount(0);
                webServerStatus.setCurrentThreadCount(0);
                webServerStatus.setCurrentThreadsBusy(0);
                // 请求数
                webServerStatus.setErrorCount(0);
                webServerStatus.setRequestCount(0);
                webServerStatus.setProcessingTime(0);
                webServerStatus.setMaxTime(0);
            }

            webServerStatus.setTime(new Date());

        } catch (Exception e) {
            webServerStatus.setStatus(false);
            logger.error("获取 web server 状态失败: ", e);
        }

        return webServerStatus;
    }

    /**
     * 获取当前 web server 内存使用列表
     *
     * @return 内存使用列表
     */
    public List<MemoryUsageStatus> getMemoryUsageStatusList() {
        // 返回内存使用列表
        List<MemoryUsageStatus> memoryUsageStatusList = Lists.newArrayList();
        try {

            SortedMap<String, MemoryPoolMXBean> memoryPoolMBeans = new TreeMap<>();
            for (MemoryPoolMXBean mbean : ManagementFactory.getMemoryPoolMXBeans()) {
                String sortKey = mbean.getType() + ":" + mbean.getName();
                memoryPoolMBeans.put(sortKey, mbean);
            }

            MemoryUsageStatus memoryUsageStatus;
            // 循环设置
            for (MemoryPoolMXBean memoryPoolMBean : memoryPoolMBeans.values()) {
                memoryUsageStatus = new MemoryUsageStatus();
                MemoryUsage usage = memoryPoolMBean.getUsage();
                logger.info("Memory Pool  is {}", memoryPoolMBean.getName());
                memoryUsageStatus.setName(memoryPoolMBean.getName());
                memoryUsageStatus.setType(memoryPoolMBean.getType().name());
                memoryUsageStatus.setInitial(usage.getInit() + "");
                memoryUsageStatus.setTotal(usage.getCommitted() + "");
                memoryUsageStatus.setUsed(usage.getUsed() + "");
                memoryUsageStatus.setMaximum(usage.getMax() + "");

                // 比率
                long rate = 0L;
                if (usage.getMax() > 0) {

                    rate = usage.getUsed() * 100 / usage.getMax();
                }
                memoryUsageStatus.setRate(rate + "");

                memoryUsageStatusList.add(memoryUsageStatus);
            }

        } catch (Exception e) {
            logger.error("获取web server内存使用列表失败: ", e);
        }

        return memoryUsageStatusList;
    }


    /**
     * 获取当前web server的请求状态列表
     *
     * @return 请求状态列表
     */
    public List<RequestProcessorStatus> getRequestProcessorStatusList() {
        // 返回请求状态列表
        List<RequestProcessorStatus> requestProcessorStatusList = Lists.newArrayList();
        try {
            String name = null;
            Enumeration<ObjectName> enumeration = threadPools.elements();
            while (enumeration.hasMoreElements()) {
                ObjectName objectName = enumeration.nextElement();

                Object tport = mBeanServer.getAttribute(objectName, "port");
                if (tport != null && StringUtils.equals(tport.toString(), port)) {
                    name = objectName.getKeyProperty("name");
                    logger.info("collect name is {}", name);
                    break;
                }
            }
            if (name == null) {
                return requestProcessorStatusList;
            }
            enumeration = requestProcessors.elements();
            while (enumeration.hasMoreElements()) {
                ObjectName pName = enumeration.nextElement();
                if (name.equals(pName.getKeyProperty("worker"))) {
                    RequestProcessorStatus requestProcessorStatus = new RequestProcessorStatus();

                    int stage = (Integer) mBeanServer.getAttribute(pName, "stage");
                    boolean fullStatus = true;
                    boolean showRequest = true;
                    String stageStr;

                    switch (stage) {

                        case (1/*org.apache.coyote.Constants.STAGE_PARSE*/):
                            stageStr = "P";
                            fullStatus = false;
                            break;
                        case (2/*org.apache.coyote.Constants.STAGE_PREPARE*/):
                            stageStr = "P";
                            fullStatus = false;
                            break;
                        case (3/*org.apache.coyote.Constants.STAGE_SERVICE*/):
                            stageStr = "S";
                            break;
                        case (4/*org.apache.coyote.Constants.STAGE_ENDINPUT*/):
                            stageStr = "F";
                            break;
                        case (5/*org.apache.coyote.Constants.STAGE_ENDOUTPUT*/):
                            stageStr = "F";
                            break;
                        case (7/*org.apache.coyote.Constants.STAGE_ENDED*/):
                            stageStr = "R";
                            fullStatus = false;
                            break;
                        case (6/*org.apache.coyote.Constants.STAGE_KEEPALIVE*/):
                            stageStr = "K";
                            fullStatus = true;
                            showRequest = false;
                            break;
                        case (0/*org.apache.coyote.Constants.STAGE_NEW*/):
                            stageStr = "R";
                            fullStatus = false;
                            break;
                        default:
                            // Unknown stage
                            stageStr = "?";
                            fullStatus = false;

                    }

                    // 设置状态名称
                    requestProcessorStatus.setStage(stageStr);

                    // 可否获取状态
                    if (fullStatus) {

                        requestProcessorStatus.setRequestProcessingTime(mBeanServer.getAttribute
                                (pName, "requestProcessingTime") + "");

                        if (showRequest) {
                            requestProcessorStatus.setRequestBytesSent(mBeanServer.getAttribute
                                    (pName, "requestBytesSent") + "");
                        } else {
                            requestProcessorStatus.setRequestBytesSent("?");
                        }
                        if (showRequest) {
                            requestProcessorStatus.setRequestBytesReceived(mBeanServer.getAttribute
                                    (pName, "requestBytesReceived") + "");
                        } else {
                            requestProcessorStatus.setRequestBytesReceived("?");
                        }

                        requestProcessorStatus.setRemoteAddrForwarded(mBeanServer.getAttribute
                                (pName, "remoteAddrForwarded").toString());
                        requestProcessorStatus.setRemoteAddr(mBeanServer.getAttribute
                                (pName, "remoteAddr").toString());
                        requestProcessorStatus.setVirtualHost(mBeanServer.getAttribute
                                (pName, "virtualHost").toString());
                        if (showRequest) {
                            requestProcessorStatus.setCurrentUri(mBeanServer.getAttribute
                                    (pName, "currentUri").toString());

                            requestProcessorStatus.setMethod(mBeanServer.getAttribute
                                    (pName, "method").toString());
                            requestProcessorStatus.setProtocol(mBeanServer.getAttribute
                                    (pName, "protocol").toString());
                            String queryString = (String) mBeanServer.getAttribute
                                    (pName, "currentQueryString");
                            if ((queryString != null) && (!queryString.equals(""))) {
                                requestProcessorStatus.setCurrentQueryString(RequestUtil.filter(queryString));
                            } else {
                                requestProcessorStatus.setCurrentQueryString("");
                            }
                        } else {
                            requestProcessorStatus.setCurrentQueryString("");
                            requestProcessorStatus.setMethod("");
                            requestProcessorStatus.setProtocol("");
                            requestProcessorStatus.setCurrentUri("");
                        }
                    } else {
                        // 设置默认值
                        requestProcessorStatus.setRequestProcessingTime("?");
                        requestProcessorStatus.setRequestBytesSent("?");
                        requestProcessorStatus.setRequestBytesReceived("?");

                        requestProcessorStatus.setRemoteAddrForwarded("?");
                        requestProcessorStatus.setRemoteAddr("?");
                        requestProcessorStatus.setVirtualHost("?");

                        requestProcessorStatus.setCurrentQueryString("");
                        requestProcessorStatus.setMethod("");
                        requestProcessorStatus.setProtocol("");
                        requestProcessorStatus.setCurrentUri("");
                    }
                    requestProcessorStatusList.add(requestProcessorStatus);
                }
            }

        } catch (Exception e) {
            logger.error("获取web server请求状态列表失败: ", e);
        }

        return requestProcessorStatusList;
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================


    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
